
/******************************************************************************
 * MODULE     : unicode_font.cpp
 * DESCRIPTION: True Type fonts (using FreeType II)
 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "unicode_font.hpp"
#include "analyze.hpp"
#include "font.hpp"
#include "scalable.hpp"
#include <lolly/data/numeral.hpp>
#include <lolly/data/unicode.hpp>

using lolly::data::decode_from_utf8;
using lolly::data::from_hex;

#define std_dpi 600
#define std_pixel (std_shrinkf * 256)
#define ROUND(l) ((l * hdpi + (std_dpi >> 1)) / std_dpi)
#define FLOOR(l) ((((l * hdpi) / std_dpi) / std_pixel) * std_pixel)
#define CEIL(l)                                                                \
  (((((l * hdpi + (std_dpi - 1)) / std_dpi) + std_pixel - 1) / std_pixel) *    \
   std_pixel)

#define LIGATURE_FF 1
#define LIGATURE_FI 2
#define LIGATURE_FL 4
#define LIGATURE_FT 8
#define LIGATURE_FFI 16
#define LIGATURE_FFL 32
#define LIGATURE_ST 64

font unicode_font (string family, int size, int hdpi, int vdpi);

hashmap<string, double> lsup_guessed_table ();
hashmap<string, double> rsub_guessed_table ();

hashmap<string, double> lsub_stix_table ();
hashmap<string, double> lsup_stix_table ();
hashmap<string, double> rsub_stix_table ();
hashmap<string, double> rsup_stix_table ();
hashmap<string, double> above_stix_table ();
hashmap<string, double> lsub_termes_table ();
hashmap<string, double> lsup_termes_table ();
hashmap<string, double> rsub_termes_table ();
hashmap<string, double> rsup_termes_table ();
hashmap<string, double> above_termes_table ();
hashmap<string, double> lsub_pagella_table ();
hashmap<string, double> lsup_pagella_table ();
hashmap<string, double> rsub_pagella_table ();
hashmap<string, double> rsup_pagella_table ();
hashmap<string, double> above_pagella_table ();
hashmap<string, double> lsub_schola_table ();
hashmap<string, double> lsup_schola_table ();
hashmap<string, double> rsub_schola_table ();
hashmap<string, double> rsup_schola_table ();
hashmap<string, double> above_schola_table ();
hashmap<string, double> lsub_bonum_table ();
hashmap<string, double> lsup_bonum_table ();
hashmap<string, double> rsub_bonum_table ();
hashmap<string, double> rsup_bonum_table ();
hashmap<string, double> above_bonum_table ();
hashmap<string, double> lsub_libertine_table ();
hashmap<string, double> lsup_libertine_table ();
hashmap<string, double> rsub_libertine_table ();
hashmap<string, double> rsup_libertine_table ();
hashmap<string, double> above_libertine_table ();
hashmap<string, double> lsub_biolinum_table ();
hashmap<string, double> lsup_biolinum_table ();
hashmap<string, double> rsub_biolinum_table ();
hashmap<string, double> rsup_biolinum_table ();
hashmap<string, double> above_biolinum_table ();
hashmap<string, double> lsub_fira_table ();
hashmap<string, double> lsup_fira_table ();
hashmap<string, double> rsub_fira_table ();
hashmap<string, double> rsup_fira_table ();
hashmap<string, double> above_fira_table ();

hashmap<string, double> lsub_stix_italic_table ();
hashmap<string, double> lsup_stix_italic_table ();
hashmap<string, double> rsub_stix_italic_table ();
hashmap<string, double> rsup_stix_italic_table ();
hashmap<string, double> above_stix_italic_table ();
hashmap<string, double> lsub_termes_italic_table ();
hashmap<string, double> lsup_termes_italic_table ();
hashmap<string, double> rsub_termes_italic_table ();
hashmap<string, double> rsup_termes_italic_table ();
hashmap<string, double> above_termes_italic_table ();
hashmap<string, double> lsub_pagella_italic_table ();
hashmap<string, double> lsup_pagella_italic_table ();
hashmap<string, double> rsub_pagella_italic_table ();
hashmap<string, double> rsup_pagella_italic_table ();
hashmap<string, double> above_pagella_italic_table ();
hashmap<string, double> lsub_schola_italic_table ();
hashmap<string, double> lsup_schola_italic_table ();
hashmap<string, double> rsub_schola_italic_table ();
hashmap<string, double> rsup_schola_italic_table ();
hashmap<string, double> above_schola_italic_table ();
hashmap<string, double> lsub_bonum_italic_table ();
hashmap<string, double> lsup_bonum_italic_table ();
hashmap<string, double> rsub_bonum_italic_table ();
hashmap<string, double> rsup_bonum_italic_table ();
hashmap<string, double> above_bonum_italic_table ();
hashmap<string, double> lsub_libertine_italic_table ();
hashmap<string, double> lsup_libertine_italic_table ();
hashmap<string, double> rsub_libertine_italic_table ();
hashmap<string, double> rsup_libertine_italic_table ();
hashmap<string, double> above_libertine_italic_table ();
hashmap<string, double> lsub_biolinum_italic_table ();
hashmap<string, double> lsup_biolinum_italic_table ();
hashmap<string, double> rsub_biolinum_italic_table ();
hashmap<string, double> rsup_biolinum_italic_table ();
hashmap<string, double> above_biolinum_italic_table ();
hashmap<string, double> lsub_fira_italic_table ();
hashmap<string, double> lsup_fira_italic_table ();
hashmap<string, double> rsub_fira_italic_table ();
hashmap<string, double> rsup_fira_italic_table ();
hashmap<string, double> above_fira_italic_table ();

/******************************************************************************
 * Initialization of main font parameters
 ******************************************************************************/

unicode_font_rep::unicode_font_rep (string name, string family2, int size2,
                                    int hdpi2, int vdpi2)
    : font_rep (name), family (family2), hdpi (hdpi2), vdpi (vdpi2), ligs (0),
      native (0) {
  type= FONT_TYPE_UNICODE;
  size= size2;
  fnm = tt_font_metric (family, size, std_dpi, (std_dpi * vdpi) / hdpi);
  fng = tt_font_glyphs (family, size, hdpi, vdpi);
  if (fnm->bad_font_metric || fng->bad_font_glyphs) {
    fnm= std_font_metric (res_name, NULL, 0, -1);
    fng= std_font_glyphs (res_name, NULL, 0, -1);
    if (DEBUG_AUTO)
      debug_fonts << "TeXmacs] Font " << family << " " << size << "pt "
                  << "at " << hdpi << " dpi could not be loaded\n";
  }

  // get main font parameters
  metric ex;
  get_extents ("f", ex);
  y1= ex->y1;
  y2= ex->y2;
  get_extents ("p", ex);
  y1= min (y1, ex->y1);
  y2= max (y2, ex->y2);
  get_extents ("d", ex);
  y1          = min (y1, ex->y1);
  y2          = max (y2, ex->y2);
  display_size= y2 - y1;
  design_size = size << 8;

  // get character dimensions
  get_extents ("x", ex);
  yx= ex->y2;
  get_extents ("M", ex);
  wquad= ex->x2;

  // compute other heights
  yfrac       = yx >> 1;
  ysub_lo_base= -yx / 3;
  ysub_hi_lim = (5 * yx) / 6;
  ysup_lo_lim = yx / 2;
  ysup_lo_base= (5 * yx) / 6;
  ysup_hi_lim = yx;
  yshift      = yx / 6;

  // compute other widths
  wpt  = (hdpi * PIXEL) / 72;
  hpt  = (vdpi * PIXEL) / 72;
  wfn  = (wpt * design_size) >> 8;
  wline= wfn / 20;

  // get fraction bar parameters; reasonable compromise between several fonts
  if (supports ("<#2212>")) get_extents ("<#2212>", ex);
  else if (supports ("+")) get_extents ("+", ex);
  else if (supports ("-")) get_extents ("-", ex);
  else get_extents ("x", ex);
  yfrac= (ex->y1 + ex->y2) >> 1;
  if (supports ("<#2212>") || supports ("+") || supports ("-")) {
    wline= ex->y2 - ex->y1;
    if (supports ("<#2212>"))
      ;
    else if (supports ("<#2013>")) {
      get_extents ("<#2013>", ex);
      wline= min (wline, ex->y2 - ex->y1);
    }
    wline= max (min (wline, wfn / 8), wfn / 48);
    if (!supports ("<#2212>")) yfrac+= wline / 4;
  }
  if (starts (res_name, "unicode:Papyrus.")) wline= (2 * wline) / 3;

  // get space length
  get_extents (" ", ex);
  spc  = space ((3 * (ex->x2 - ex->x1)) >> 2, ex->x2 - ex->x1,
                (3 * (ex->x2 - ex->x1)) >> 1);
  extra= spc / 2;
  mspc = spc;
  sep  = wfn / 10;

  // get_italic space
  get_extents ("f", ex);
  SI italic_spc= (ex->x4 - ex->x3) - (ex->x2 - ex->x1);
  slope        = ((double) italic_spc) / ((double) display_size) - 0.05;
  if (slope < 0.15) slope= 0.0;

  // determine whether we are dealing with a monospaced font
  get_extents ("m", ex);
  SI em= ex->x2 - ex->x1;
  get_extents ("i", ex);
  SI   ei  = ex->x2 - ex->x1;
  bool mono= (em == ei);

  // available standard ligatures
  if (!mono) {
    if (fnm->exists (0xfb00)) ligs+= LIGATURE_FF;
    if (fnm->exists (0xfb01)) ligs+= LIGATURE_FI;
    if (fnm->exists (0xfb02)) ligs+= LIGATURE_FL;
    if (fnm->exists (0xfb03)) ligs+= LIGATURE_FFI;
    if (fnm->exists (0xfb04)) ligs+= LIGATURE_FFL;
    if (fnm->exists (0xfb05)) ligs+= LIGATURE_FT;
    // if (fnm->exists (0xfb06)) ligs += LIGATURE_ST;
  }
  if (family == "Times New Roman") ligs= LIGATURE_FI + LIGATURE_FL;
  if (family == "Zapfino")
    ligs= LIGATURE_FF + LIGATURE_FI + LIGATURE_FL + LIGATURE_FFI;
  // cout << "ligs= " << ligs << ", " << family << ", " << size << "\n";

  // direct translations for certain characters without Unicode names
  if (starts (family, "texgyre") && ends (family, "-math"))
    tex_gyre_operators ();

  if (starts (family, "STIX-")) {
    if (!ends (family, "italic")) {
      global_rsub_correct= (SI) (0.04 * wfn);
      global_rsup_correct= (SI) (0.04 * wfn);
      lsub_correct       = lsub_stix_table ();
      lsup_correct       = lsup_stix_table ();
      rsub_correct       = rsub_stix_table ();
      rsup_correct       = rsup_stix_table ();
      above_correct      = above_stix_table ();
    }
    else {
      global_rsub_correct= (SI) (0.04 * wfn);
      global_rsup_correct= (SI) (0.04 * wfn);
      lsub_correct       = lsub_stix_italic_table ();
      lsup_correct       = lsup_stix_italic_table ();
      rsub_correct       = rsub_stix_italic_table ();
      rsup_correct       = rsup_stix_italic_table ();
      above_correct      = above_stix_italic_table ();
    }
  }

  else if (starts (family, "texgyre")) {
    if (!ends (family, "italic")) {
      if (starts (family, "texgyretermes-")) {
        global_rsup_correct= (SI) (0.04 * wfn);
        lsub_correct       = lsub_termes_table ();
        lsup_correct       = lsup_termes_table ();
        rsub_correct       = rsub_termes_table ();
        rsup_correct       = rsup_termes_table ();
        above_correct      = above_termes_table ();
      }
      else if (starts (family, "texgyrepagella-")) {
        global_rsub_correct= (SI) (0.03 * wfn);
        global_rsup_correct= (SI) (0.03 * wfn);
        lsub_correct       = lsub_pagella_table ();
        lsup_correct       = lsup_pagella_table ();
        rsub_correct       = rsub_pagella_table ();
        rsup_correct       = rsup_pagella_table ();
        above_correct      = above_pagella_table ();
      }
      else if (starts (family, "texgyreschola-")) {
        lsub_correct = lsub_schola_table ();
        lsup_correct = lsup_schola_table ();
        rsub_correct = rsub_schola_table ();
        rsup_correct = rsup_schola_table ();
        above_correct= above_schola_table ();
      }
      else if (starts (family, "texgyrebonum-")) {
        lsub_correct = lsub_bonum_table ();
        lsup_correct = lsup_bonum_table ();
        rsub_correct = rsub_bonum_table ();
        rsup_correct = rsup_bonum_table ();
        above_correct= above_bonum_table ();
      }
    }
    else {
      if (starts (family, "texgyretermes-")) {
        global_rsup_correct= (SI) (0.04 * wfn);
        lsub_correct       = lsub_termes_italic_table ();
        lsup_correct       = lsup_termes_italic_table ();
        rsub_correct       = rsub_termes_italic_table ();
        rsup_correct       = rsup_termes_italic_table ();
        above_correct      = above_termes_italic_table ();
      }
      else if (starts (family, "texgyrepagella-")) {
        global_rsub_correct= (SI) (0.03 * wfn);
        global_rsup_correct= (SI) (0.03 * wfn);
        lsub_correct       = lsub_pagella_italic_table ();
        lsup_correct       = lsup_pagella_italic_table ();
        rsub_correct       = rsub_pagella_italic_table ();
        rsup_correct       = rsup_pagella_italic_table ();
        above_correct      = above_pagella_italic_table ();
      }
      else if (starts (family, "texgyreschola-")) {
        lsub_correct = lsub_schola_italic_table ();
        lsup_correct = lsup_schola_italic_table ();
        rsub_correct = rsub_schola_italic_table ();
        rsup_correct = rsup_schola_italic_table ();
        above_correct= above_schola_italic_table ();
      }
      else if (starts (family, "texgyrebonum-")) {
        lsub_correct = lsub_bonum_italic_table ();
        lsup_correct = lsup_bonum_italic_table ();
        rsub_correct = rsub_bonum_italic_table ();
        rsup_correct = rsup_bonum_italic_table ();
        above_correct= above_bonum_italic_table ();
      }
    }
    if (starts (family, "texgyrepagella-")) mspc= spc + 0.5 * space (spc->def);
  }

  else if (starts (family, "Papyrus")) {
    lsup_correct= copy (lsup_guessed_table ());
    rsub_correct= copy (rsub_guessed_table ());
    adjust_integral (lsup_correct, "1", -0.15);
    adjust_integral (lsup_correct, "2", -0.15);
    adjust_integral (rsub_correct, "1", 0.15);
    adjust_integral (rsub_correct, "2", 0.15);
  }

  else if (starts (family, "LinLibertine")) {
    if (!ends (family, "I")) {
      lsub_correct = lsub_libertine_table ();
      lsup_correct = lsup_libertine_table ();
      rsub_correct = rsub_libertine_table ();
      rsup_correct = rsup_libertine_table ();
      above_correct= above_libertine_table ();
    }
    else {
      lsub_correct = lsub_libertine_italic_table ();
      lsup_correct = lsup_libertine_italic_table ();
      rsub_correct = rsub_libertine_italic_table ();
      rsup_correct = rsup_libertine_italic_table ();
      above_correct= above_libertine_italic_table ();
    }
    if (starts (family, "LinLibertine_a")) ligs= 0;
  }
  else if (starts (family, "LinBiolinum")) {
    if (!ends (family, "I")) {
      lsub_correct = lsub_biolinum_table ();
      lsup_correct = lsup_biolinum_table ();
      rsub_correct = rsub_biolinum_table ();
      rsup_correct = rsup_biolinum_table ();
      above_correct= above_biolinum_table ();
    }
    else {
      lsub_correct = lsub_biolinum_italic_table ();
      lsup_correct = lsup_biolinum_italic_table ();
      rsub_correct = rsub_biolinum_italic_table ();
      rsup_correct = rsup_biolinum_italic_table ();
      above_correct= above_biolinum_italic_table ();
    }
    if (starts (family, "LinBiolinum_a")) ligs= 0;
  }
  else if (starts (family, "FiraSans")) {
    if (!ends (family, "Italic")) {
      lsub_correct = lsub_fira_table ();
      lsup_correct = lsup_fira_table ();
      rsub_correct = rsub_fira_table ();
      rsup_correct = rsup_fira_table ();
      above_correct= above_fira_table ();
    }
    else {
      lsub_correct = lsub_fira_italic_table ();
      lsup_correct = lsup_fira_italic_table ();
      rsub_correct = rsub_fira_italic_table ();
      rsup_correct = rsup_fira_italic_table ();
      above_correct= above_fira_italic_table ();
    }
  }
  else {
    // try to get OpenType math table
    tt_face math_face2= tt_face (family);
    if (!is_nil (math_face2->math_table)) {
      this->math_face = math_face2;
      this->math_table= math_face2->math_table;
      math_type       = MATH_TYPE_OPENTYPE;
      init_design_unit_factor ();
      // limit boxes
      upper_limit_gap_min=
          design_unit_to_metric (math_table->constants_table[upperLimitGapMin]);
      upper_limit_baseline_rise_min= design_unit_to_metric (
          math_table->constants_table[upperLimitBaselineRiseMin]);
      lower_limit_gap_min=
          design_unit_to_metric (math_table->constants_table[lowerLimitGapMin]);
      lower_limit_baseline_drop_min= design_unit_to_metric (
          math_table->constants_table[lowerLimitBaselineDropMin]);
      // frac boxes
      frac_rule_thickness= design_unit_to_metric (
          math_table->constants_table[fractionRuleThickness]);
      frac_num_shift_up= design_unit_to_metric (
          math_table->constants_table[fractionNumeratorShiftUp]);
      frac_num_disp_shift_up= design_unit_to_metric (
          math_table->constants_table[fractionNumeratorDisplayStyleShiftUp]);
      frac_num_gap_min= design_unit_to_metric (
          math_table->constants_table[fractionNumeratorGapMin]);
      frac_num_disp_gap_min= design_unit_to_metric (
          math_table->constants_table[fractionNumDisplayStyleGapMin]);
      frac_denom_shift_down= design_unit_to_metric (
          math_table->constants_table[fractionDenominatorShiftDown]);
      frac_denom_disp_shift_down= design_unit_to_metric (
          math_table
              ->constants_table[fractionDenominatorDisplayStyleShiftDown]);
      frac_denom_gap_min= design_unit_to_metric (
          math_table->constants_table[fractionDenominatorGapMin]);
      frac_denom_disp_gap_min= design_unit_to_metric (
          math_table->constants_table[fractionDenominatorGapMin]);
      // sqrt boxes
      sqrt_ver_gap= design_unit_to_metric (
          math_table->constants_table[radicalVerticalGap]);
      sqrt_ver_disp_gap= design_unit_to_metric (
          math_table->constants_table[radicalDisplayStyleVerticalGap]);
      sqrt_rule_thickness= design_unit_to_metric (
          math_table->constants_table[radicalRuleThickness]);
      sqrt_extra_ascender= design_unit_to_metric (
          math_table->constants_table[radicalExtraAscender]);
      sqrt_degree_rise_percent=
          math_table->constants_table[radicalDegreeBottomRaisePercent];
      sqrt_kern_before_degree= design_unit_to_metric (
          math_table->constants_table[radicalKernBeforeDegree]);
      sqrt_kern_after_degree= design_unit_to_metric (
          math_table->constants_table[radicalKernAfterDegree]);
    }
  }
}

/******************************************************************************
 * Big operators in TeX Gyre fonts
 ******************************************************************************/

static void
bracket (hashmap<string, int>& h, string c, int n1, int n2, int im, int d) {
  for (int n= n1; n <= n2; n++, im+= d) {
    string s         = c * "-" * as_string (n) * ">";
    h ("<large-" * s)= im;
    h ("<left-" * s) = im;
    h ("<mid-" * s)  = im;
    h ("<right-" * s)= im;
  }
}

static void
wide (hashmap<string, int>& h, string c, int n1, int n2, int im, int d) {
  for (int n= n1; n <= n2; n++, im+= d) {
    string s        = c * "-" * as_string (n) * ">";
    h ("<wide-" * s)= im;
  }
}

static hashmap<string, int>
tex_gyre_native () {
  static hashmap<string, int> native;
  if (N (native) != 0) return native;
  native ("<big-prod-2>")       = 4215;
  native ("<big-amalg-2>")      = 4216;
  native ("<big-sum-2>")        = 4217;
  native ("<big-int-2>")        = 4149;
  native ("<big-iint-2>")       = 4150;
  native ("<big-iiint-2>")      = 4151;
  native ("<big-iiiint-2>")     = 4152;
  native ("<big-oint-2>")       = 4153;
  native ("<big-oiint-2>")      = 4154;
  native ("<big-oiiint-2>")     = 4155;
  native ("<big-wedge-2>")      = 3833;
  native ("<big-vee-2>")        = 3835;
  native ("<big-cap-2>")        = 3827;
  native ("<big-cup-2>")        = 3829;
  native ("<big-odot-2>")       = 3864;
  native ("<big-oplus-2>")      = 3868;
  native ("<big-otimes-2>")     = 3873;
  native ("<big-pluscup-2>")    = 3861;
  native ("<big-sqcap-2>")      = 3852;
  native ("<big-sqcup-2>")      = 3854;
  native ("<big-intlim-2>")     = 4149;
  native ("<big-iintlim-2>")    = 4150;
  native ("<big-iiintlim-2>")   = 4151;
  native ("<big-iiiintlim-2>")  = 4152;
  native ("<big-ointlim-2>")    = 4153;
  native ("<big-oiintlim-2>")   = 4154;
  native ("<big-oiiintlim-2>")  = 4155;
  native ("<big-upint-2>")      = 4149;
  native ("<big-upiint-2>")     = 4150;
  native ("<big-upiiint-2>")    = 4151;
  native ("<big-upiiiint-2>")   = 4152;
  native ("<big-upoint-2>")     = 4153;
  native ("<big-upoiint-2>")    = 4154;
  native ("<big-upoiiint-2>")   = 4155;
  native ("<big-upintlim-2>")   = 4149;
  native ("<big-upiintlim-2>")  = 4150;
  native ("<big-upiiintlim-2>") = 4151;
  native ("<big-upiiiintlim-2>")= 4152;
  native ("<big-upointlim-2>")  = 4153;
  native ("<big-upoiintlim-2>") = 4154;
  native ("<big-upoiiintlim-2>")= 4155;

  native ("<large-sqrt-1>")= 4136;
  native ("<large-sqrt-2>")= 4148;
  native ("<large-sqrt-3>")= 4160;
  native ("<large-sqrt-4>")= 4172;
  native ("<large-sqrt-5>")= 4184;
  native ("<large-sqrt-6>")= 4196;

  bracket (native, "(", 1, 5, 3461, 22);
  bracket (native, ")", 1, 5, 3462, 22);
  bracket (native, "{", 1, 5, 3465, 22);
  bracket (native, "}", 1, 5, 3466, 22);
  bracket (native, "[", 1, 5, 3467, 22);
  bracket (native, "]", 1, 5, 3468, 22);
  bracket (native, "lceil", 1, 5, 3469, 22);
  bracket (native, "rceil", 1, 5, 3470, 22);
  bracket (native, "lfloor", 1, 5, 3471, 22);
  bracket (native, "rfloor", 1, 5, 3472, 22);
  bracket (native, "llbracket", 1, 5, 3473, 22);
  bracket (native, "rrbracket", 1, 5, 3474, 22);
  bracket (native, "langle", 1, 6, 3655, 4);
  bracket (native, "rangle", 1, 6, 3656, 4);
  bracket (native, "llangle", 1, 6, 3657, 4);
  bracket (native, "rrangle", 1, 6, 3658, 4);
  bracket (native, "/", 1, 6, 3742, 7);
  bracket (native, "\\", 1, 6, 3743, 7);
  bracket (native, "|", 1, 6, 3745, 7);
  bracket (native, "||", 1, 6, 3746, 7);

  native ("<wide-hat-0>")  = 125;
  native ("<wide-tilde-0>")= 126;
  native ("<wide-breve-0>")= 128;
  native ("<wide-check-0>")= 135;
  wide (native, "breve", 1, 6, 3378, 10);
  wide (native, "invbreve", 1, 6, 3380, 10);
  wide (native, "check", 1, 6, 3382, 10);
  wide (native, "hat", 1, 6, 3384, 10);
  wide (native, "tilde", 1, 6, 3386, 10);
  wide (native, "overbrace", 0, 5, 3453, 22);
  wide (native, "overbrace*", 0, 5, 3453, 22);
  wide (native, "underbrace", 0, 5, 3454, 22);
  wide (native, "underbrace*", 0, 5, 3454, 22);
  wide (native, "poverbrace", 0, 5, 3455, 22);
  wide (native, "poverbrace*", 0, 5, 3455, 22);
  wide (native, "punderbrace", 0, 5, 3456, 22);
  wide (native, "punderbrace*", 0, 5, 3456, 22);
  wide (native, "sqoverbrace", 0, 5, 3457, 22);
  wide (native, "sqoverbrace*", 0, 5, 3457, 22);
  wide (native, "squnderbrace", 0, 5, 3458, 22);
  wide (native, "squnderbrace*", 0, 5, 3458, 22);
  return native;
}

void
unicode_font_rep::tex_gyre_operators () {
  native= tex_gyre_native ();
}

/******************************************************************************
 * Routines for font
 ******************************************************************************/

unsigned int
unicode_font_rep::read_unicode_char (string s, int& i) {
  if (s[i] == '<') {
    i++;
    int start= i, n= N (s);
    while (true) {
      if (i == n) {
        i= start;
        return (int) '<';
      }
      if (s[i] == '>') break;
      i++;
    }
    if (s[start] == '#') {
      start++;
      return (unsigned int) from_hex (s (start, i++));
    }
    else if (s[start] == '@') {
      // <@XXXX> are native glyph ids generated by rubber_unicode_font
      start++;
      return 0xc000000 + (unsigned int) from_hex (s (start, i++));
    }
    else {
      string ss= s (start - 1, ++i);
      string uu= strict_cork_to_utf8 (ss);
      if (uu == ss) {
        if (native->contains (ss)) return 0xc000000 + native[ss];
        return 0;
      }
      int j= 0;
      return decode_from_utf8 (uu, j);
    }
  }
  else {
    unsigned int c= (unsigned int) s[i++];
    if (c >= 32 && c <= 127 && c != 0x27 && c != 0x60) return c;
    string ss= s (i - 1, i);
    string uu= strict_cork_to_utf8 (ss);
    int    j = 0;
    return decode_from_utf8 (uu, j);
  }
}

unsigned int
unicode_font_rep::ligature_replace (unsigned int uc, string s, int& i) {
  int n= N (s);
  if (((char) uc) == 'f') {
    if (i < n && s[i] == 'i' && (ligs & LIGATURE_FI) != 0) {
      i++;
      return 0xfb01;
    }
    else if (i < n && s[i] == 'l' && (ligs & LIGATURE_FL) != 0) {
      i++;
      return 0xfb02;
    }
    else if (i < n && s[i] == 't' && (ligs & LIGATURE_FT) != 0) {
      i++;
      return 0xfb05;
    }
    else if ((i + 1) < n && s[i] == 'f' && s[i + 1] == 'i' &&
             (ligs & LIGATURE_FFI) != 0) {
      i+= 2;
      return 0xfb03;
    }
    else if ((i + 1) < n && s[i] == 'f' && s[i + 1] == 'l' &&
             (ligs & LIGATURE_FFL) != 0) {
      i+= 2;
      return 0xfb04;
    }
    else if (i < n && s[i] == 'f' && (ligs & LIGATURE_FF) != 0) {
      i++;
      return 0xfb00;
    }
    else return uc;
  }
  else if (((char) uc) == 's') {
    if (i < n && s[i] == 't' && (ligs & LIGATURE_ST) != 0) {
      i++;
      return 0xfb06;
    }
    else return uc;
  }
  else return uc;
}

bool
unicode_font_rep::supports (string c) {
  if (N (c) == 0) return false;
  int          i = 0;
  unsigned int uc= read_unicode_char (c, i);
  if (uc == 0 || !fnm->exists (uc)) return false;
  if (uc >= 0x42 && uc <= 0x5a && !fnm->exists (0x41)) return false;
  if (uc >= 0x62 && uc <= 0x7a && !fnm->exists (0x61)) return false;
  metric_struct* m= fnm->get (uc);
  return m->x1 < m->x2 && m->y1 <= m->y2;
}

void
unicode_font_rep::get_extents (string s, metric& ex) {
  if (N (s) == 0) {
    ex->x1= ex->x3= ex->x2= ex->x4= 0;
    ex->y3= ex->y1= 0;
    ex->y4= ex->y2= yx;
  }
  else {
    int          i= 0, n= N (s);
    unsigned int uc= read_unicode_char (s, i);
    if (ligs > 0 && (((char) uc) == 'f' || ((char) uc) == 's'))
      uc= ligature_replace (uc, s, i);
    metric_struct* first= fnm->get (uc);
    ex->x1              = ROUND (first->x1);
    ex->y1              = ROUND (first->y1);
    ex->x2              = ROUND (first->x2);
    ex->y2              = ROUND (first->y2);
    ex->x3              = FLOOR (first->x3);
    ex->y3              = FLOOR (first->y3);
    ex->x4              = CEIL (first->x4);
    ex->y4              = CEIL (first->y4);
    SI x                = ROUND (first->x2);

    while (i < n) {
      unsigned int pc= uc;
      uc             = read_unicode_char (s, i);
      if (ligs > 0 && (((char) uc) == 'f' || ((char) uc) == 's'))
        uc= ligature_replace (uc, s, i);
      x+= ROUND (fnm->kerning (pc, uc));
      metric_struct* next= fnm->get (uc);
      ex->x1             = min (ex->x1, x + ROUND (next->x1));
      ex->y1             = min (ex->y1, ROUND (next->y1));
      ex->x2             = max (ex->x2, x + ROUND (next->x2));
      ex->y2             = max (ex->y2, ROUND (next->y2));
      ex->x3             = min (ex->x3, x + FLOOR (next->x3));
      ex->y3             = min (ex->y3, FLOOR (next->y3));
      ex->x4             = max (ex->x4, x + CEIL (next->x4));
      ex->y4             = max (ex->y4, CEIL (next->y4));
      x+= ROUND (next->x2);
      // if (fnm->kerning (pc, uc) != 0)
      // cout << "Kerning " << ((char) pc) << ((char) uc) << " " << ROUND
      // (fnm->kerning (pc, uc)) << ", " << ROUND (next->x2) << "\n";
    }
  }
}

void
unicode_font_rep::get_xpositions (string s, SI* xpos, bool ligf) {
  int i= 0, n= N (s);
  if (n == 0) {
    xpos[0]= 0;
    return;
  }

  SI           x = 0;
  unsigned int uc= 0xffffffff;
  while (i < n) {
    int          start= i;
    unsigned int pc   = uc;
    uc                = read_unicode_char (s, i);
    if (ligs > 0 && ligf && (((char) uc) == 'f' || ((char) uc) == 's'))
      uc= ligature_replace (uc, s, i);
    if (pc != 0xffffffff) x+= ROUND (fnm->kerning (pc, uc));
    metric_struct* next= fnm->get (uc);
    for (int j= start; j < i; j++)
      xpos[j]= x;
    x+= ROUND (next->x2);
    // if (fnm->kerning (pc, uc) != 0)
    // cout << "Kerning " << ((char) pc) << ((char) uc) << " " << ROUND
    // (fnm->kerning (pc, uc)) << ", " << ROUND (next->x2) << "\n";
  }
  xpos[n]= x;
}

void
unicode_font_rep::get_xpositions (string s, SI* xpos) {
  get_xpositions (s, xpos, true);
}

void
unicode_font_rep::draw_fixed (renderer ren, string s, SI x, SI y, bool ligf) {
  int          i= 0, n= N (s);
  unsigned int uc= 0xffffffff;
  while (i < n) {
    unsigned int pc= uc;
    uc             = read_unicode_char (s, i);
    if (ligs > 0 && ligf && (((char) uc) == 'f' || ((char) uc) == 's'))
      uc= ligature_replace (uc, s, i);
    if (pc != 0xffffffff) x+= ROUND (fnm->kerning (pc, uc));
    ren->draw (uc, fng, x, y);
    metric_struct* ex= fnm->get (uc);
    x+= ROUND (ex->x2);
    // if (fnm->kerning (pc, uc) != 0)
    // cout << "Kerning " << ((char) pc) << ((char) uc) << " " << ROUND
    // (fnm->kerning (pc, uc)) << ", " << ROUND (ex->x2) << "\n";
  }
}

void
unicode_font_rep::draw_fixed (renderer ren, string s, SI x, SI y) {
  draw_fixed (ren, s, x, y, true);
}

font
unicode_font_rep::magnify (double zoomx, double zoomy) {
  return unicode_font (family, size, (int) tm_round (hdpi * zoomx),
                       (int) tm_round (vdpi * zoomy));
}

void
unicode_font_rep::advance_glyph (string s, int& pos, bool ligf) {
  if (pos >= N (s)) return;
  unsigned int uc= read_unicode_char (s, pos);
  if (ligs > 0 && ligf && (((char) uc) == 'f' || ((char) uc) == 's'))
    uc= ligature_replace (uc, s, pos);
}

glyph
unicode_font_rep::get_glyph (string s) {
  int i= 0, n= N (s);
  if (n == 0) return font_rep::get_glyph (s);
  unsigned int uc= read_unicode_char (s, i);
  if (ligs > 0 && (((char) uc) == 'f' || ((char) uc) == 's'))
    uc= ligature_replace (uc, s, i);
  if (i != n) return font_rep::get_glyph (s);
  glyph gl= fng->get (uc);
  if (is_nil (gl)) return font_rep::get_glyph (s);
  return gl;
}

int
unicode_font_rep::index_glyph (string s, font_metric& rm, font_glyphs& rg) {
  int i= 0, n= N (s);
  if (n == 0) return font_rep::index_glyph (s, rm, rg);
  unsigned int uc= read_unicode_char (s, i);
  if (ligs > 0 && (((char) uc) == 'f' || ((char) uc) == 's'))
    uc= ligature_replace (uc, s, i);
  if (i != n) return font_rep::index_glyph (s, rm, rg);
  glyph gl= fng->get (uc);
  if (is_nil (gl)) return font_rep::index_glyph (s, rm, rg);
  rm= fnm;
  rg= fng;
  return uc;
}

static bool
is_math_italic (string c) {
  int c_N= N (c);
  if (c_N <= 2 || c_N > 6) {
    // the largest unicode is U+10FFFF
    return false;
  }
  int i   = 0;
  int code= decode_from_utf8 (strict_cork_to_utf8 (c), i);
  if (code < 0x2100 || code > 0x1d7ff) return false;
  if (code <= 0x213a) {
    if (code == 0x210a || code == 0x210b || code == 0x210e || code == 0x210f ||
        code == 0x2110 || code == 0x2112 || code == 0x2113 || code == 0x211b ||
        code == 0x212c || code == 0x212f || code == 0x2130 || code == 0x2131 ||
        code == 0x2133 || code == 0x2134)
      return true;
  }
  else if (code >= 0x1d400) {
    if (code >= 0x1d434 && code <= 0x1d503) return true;
    if (code >= 0x1d608 && code <= 0x1d66f) return true;
    if (code >= 0x1d6e2 && code <= 0x1d755) return true;
    if (code >= 0x1d790 && code <= 0x1d7c9) return true;
  }
  return false;
}

static bool
is_integral (string s) {
  if (!starts (s, "<big-")) return false;
  int pos= 5, n= N (s);
  if (pos + 1 < n && s[pos] == 'u' && s[pos + 1] == 'p') pos+= 2;
  if (pos < n && s[pos] == 'o') pos++;
  while (pos + 1 < n && s[pos] == 'i' && s[pos + 1] == 'i')
    pos++;
  return test (s, pos, "int-") || test (s, pos, "int>") ||
         test (s, pos, "idotsint");
}

static bool
is_alt_integral (string s) {
  if (!starts (s, "<")) return false;
  int pos= 1, n= N (s);
  if (pos + 1 < n && s[pos] == 'u' && s[pos + 1] == 'p') pos+= 2;
  if (pos < n && s[pos] == 'o') pos++;
  while (pos + 1 < n && s[pos] == 'i' && s[pos + 1] == 'i')
    pos++;
  return test (s, pos, "int>") || test (s, pos, "idotsint");
}

double
unicode_font_rep::get_left_slope (string s) {
  if (N (s) == 0) return slope;
  int pos= 0;
  tm_char_forwards (s, pos);
  if (pos == 1) return slope;
  metric ex;
  string c= s (pos, N (s));
  if (N (c) >= 3) {
    if (is_math_italic (c))
      return max (slope, 0.2); // FIXME: should be determined more reliably
    else if (math_type == MATH_TYPE_TEX_GYRE && is_integral (s)) return 0.1;
  }
  get_extents (c, ex);
  if (ex->y3 >= 0) return slope;
  double sl= ((double) (ex->x3 - ex->x1)) / ((double) ex->y3);
  if (sl > slope + 0.05) return sl;
  else return slope;
}

double
unicode_font_rep::get_right_slope (string s) {
  if (N (s) == 0) return slope;
  int pos= N (s);
  tm_char_backwards (s, pos);
  if (pos == N (s) - 1) return slope;
  metric ex;
  string c= s (pos, N (s));
  if (N (c) >= 3) {
    if (is_math_italic (c))
      return max (slope, 0.2); // FIXME: should be determined more reliably
    else if (math_type == MATH_TYPE_TEX_GYRE && is_integral (s)) return 0.1;
  }
  get_extents (c, ex);
  if (ex->y4 <= 0) return slope;
  double sl= ((double) (ex->x4 - ex->x2)) / ((double) ex->y4);
  if (sl > slope + 0.05) return sl;
  else return slope;
}

SI
unicode_font_rep::get_left_correction (string s) {
  metric ex;
  get_extents (s, ex);
  if (math_type == MATH_TYPE_TEX_GYRE && is_integral (s))
    return -(((ex->x2 - ex->x1) / 16));
  else if (ex->x3 < ex->x1) return ex->x1 - ex->x3;
  return 0;
}

SI
unicode_font_rep::get_right_correction (string s) {
  if (math_type == MATH_TYPE_OPENTYPE) {
    SI r= 0;
    if (get_ot_italic_correction (s, r)) return r;
  }
  metric ex;
  get_extents (s, ex);
  if (math_type == MATH_TYPE_TEX_GYRE && is_integral (s))
    return (ex->x2 - ex->x1) / 16;
  else if (ex->x4 > ex->x2) return ex->x4 - ex->x2;
  return 0;
}

SI
unicode_font_rep::get_lsub_correction (string s) {
  if (math_type == MATH_TYPE_OPENTYPE) {
    SI r= 0;
    if (get_ot_kerning (s, y1, false, true, r)) return r;
  }
  SI r= -get_left_correction (s) + global_lsub_correct;
  if (math_type == MATH_TYPE_STIX && (is_integral (s) || is_alt_integral (s)))
    ;
  else if (lsub_correct->contains (s)) r+= (SI) (lsub_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[0]) && lsub_correct->contains (s (0, 1)))
    r+= (SI) (lsub_correct[s (0, 1)] * wfn);
  return r;
}

SI
unicode_font_rep::get_lsup_correction (string s) {
  if (math_type == MATH_TYPE_OPENTYPE) {
    SI r= 0;
    if (get_ot_kerning (s, y2, true, true, r)) return r;
  }
  SI r= global_lsup_correct;
  if (math_type == MATH_TYPE_STIX && (is_integral (s) || is_alt_integral (s)))
    r+= get_right_correction (s);
  else if (lsup_correct->contains (s)) r+= (SI) (lsup_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[0]) && lsup_correct->contains (s (0, 1)))
    r+= (SI) (lsup_correct[s (0, 1)] * wfn);
  return r;
}

SI
unicode_font_rep::get_rsub_correction (string s) {
  if (math_type == MATH_TYPE_OPENTYPE) {
    SI   ic= 0, kern= 0;
    bool has_ic  = get_ot_italic_correction (s, ic);
    bool has_kern= get_ot_kerning (s, y1, false, false, kern);

    if (has_ic || has_kern) {
      // for integral, we use 3/5 of italic correction for rsub, otherwise 0
      ic  = is_ot_integral (s) ? (SI) (0.6 * ic) : 0;
      SI r= -ic + kern;
      // cout << "get_rsup_correction for: " << s << " " << rr << LF;
      return r;
    }
  }
  SI r= global_rsub_correct;
  if (math_type == MATH_TYPE_STIX && (is_integral (s) || is_alt_integral (s)))
    ;
  else if (rsub_correct->contains (s)) r+= (SI) (rsub_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[N (s) - 1]) &&
           rsub_correct->contains (s (N (s) - 1, N (s))))
    r+= (SI) (rsub_correct[s (N (s) - 1, N (s))] * wfn);
  return r;
}

SI
unicode_font_rep::get_rsup_correction (string s) {
  // cout << "Check " << s << ", " << rsup_correct[s] << ", " << this->res_name
  // << LF;
  if (math_type == MATH_TYPE_OPENTYPE) {
    SI   ic= 0, kern= 0;
    bool has_ic  = get_ot_italic_correction (s, ic);
    bool has_kern= get_ot_kerning (s, y2, true, false, kern);

    if (has_ic || has_kern) {
      // for integral signs, we use 2/5 of italic correction for rsup
      if (is_ot_integral (s)) ic= (SI) (0.4 * ic);
      SI r= ic + kern;
      // cout << "get_rsup_correction for: " << s << " " << rr << LF;
      return r;
    }
  }
  SI r= get_right_correction (s) + global_rsup_correct;
  if (math_type == MATH_TYPE_STIX && (is_integral (s) || is_alt_integral (s)))
    ;
  else if (rsup_correct->contains (s)) r+= (SI) (rsup_correct[s] * wfn);
  else if (N (s) > 1 && is_alpha (s[N (s) - 1]) &&
           rsup_correct->contains (s (N (s) - 1, N (s))))
    r+= (SI) (rsup_correct[s (N (s) - 1, N (s))] * wfn);
  return r;
}

SI
unicode_font_rep::get_wide_correction (string s, int mode) {
  if (mode > 0 && above_correct->contains (s)) {
    // cout << s << " ~> " << ((SI) (above_correct[s] * wfn)) << LF;
    return (SI) (above_correct[s] * wfn);
  }
  else if (mode < 0 && below_correct->contains (s))
    return (SI) (below_correct[s] * wfn);
  else return 0;
}

/******************************************************************************
 * Interface
 ******************************************************************************/

font
unicode_font (string family, int size, int hdpi, int vdpi) {
  string name= "unicode:" * family * as_string (size) * "@" * as_string (hdpi);
  if (vdpi != hdpi) name << "x" << as_string (vdpi);
  return make (font, name,
               tm_new<unicode_font_rep> (name, family, size, hdpi, vdpi));
}

/******************************************************************************
 * OpenType
 ******************************************************************************/

inline void
unicode_font_rep::init_design_unit_factor () {
  int units_of_m= 0;
  SI  em        = 0;
  // get the design units of the width of 'm'
  FT_UInt glyph_index= FT_Get_Char_Index (math_face->ft_face, 'm');
  FT_Load_Glyph (math_face->ft_face, glyph_index, FT_LOAD_NO_SCALE);
  units_of_m= math_face->ft_face->glyph->metrics.horiAdvance;

  // get the width of the character 'm'
  metric ex;
  get_extents ("m", ex);
  em= ex->x2 - ex->x1;

  metric_to_design_unit_factor= (double) units_of_m / (double) em;
  design_unit_to_metric_factor= (double) em / (double) units_of_m;
}

inline SI
unicode_font_rep::design_unit_to_metric (int du) {
  return (SI) (design_unit_to_metric_factor * du);
}

inline int
unicode_font_rep::metric_to_design_unit (SI m) {
  return (int) (metric_to_design_unit_factor * m);
}

font
unicode_font_rep::make_rubber_font (font base) {
  if (!is_nil (this->math_table)) {
    return rubber_unicode_font (this, this->math_face);
  }
  return font_rep::make_rubber_font (base);
}

inline int
decode_index (FT_Face face, int i) {
  if (i < 0xc000000) return ft_get_char_index (face, i);
  return i - 0xc000000;
}

inline unsigned int
unicode_font_rep::get_glyphID (string s) {
  // <@XXXX>
  if (starts (s, "<@")) {
    return from_hex (s (2, 6));
  }
  font_metric fm;
  font_glyphs fg;
  int         index= index_glyph (s, fm, fg);
  return decode_index (math_face->ft_face, index);
}

inline string
get_left (string s) {
  if (N (s) == 0) return s;
  int i= 0;
  tm_char_forwards (s, i);
  return s (0, i);
}

inline string
get_right (string s) {
  if (N (s) == 0) return s;
  int i= N (s);
  tm_char_backwards (s, i);
  return s (i, N (s));
}

bool
unicode_font_rep::get_ot_italic_correction (string s, SI& r) {
  if (math_type != MATH_TYPE_OPENTYPE || N (s) == 0) return false;

  auto italics_correction= math_table->italics_correction;
  // italic correction is only available for right side of the glyph
  string       ss     = get_right (s);
  unsigned int glyphID= get_glyphID (ss);

  if (italics_correction->contains (glyphID)) {
    int correction= italics_correction[glyphID].value;
    r             = design_unit_to_metric (correction);
    return true;
  }
  return false;
}

bool
unicode_font_rep::get_ot_kerning (string s, SI height, bool top, bool left,
                                  SI& kerning) {
  if (math_type != MATH_TYPE_OPENTYPE || N (s) == 0) return false;

  string       ss     = left ? get_left (s) : get_right (s);
  unsigned int glyphID= get_glyphID (ss);

  if (!math_table->has_kerning (glyphID, top, left)) return false;

  int kerning_unit= math_table->get_kerning (
      glyphID, metric_to_design_unit (height), top, left);

  kerning= design_unit_to_metric (kerning_unit);
  // cout << "Kerning for " << ss << " with height: " << kerning_unit << " -> "
  //      << kerning << LF;
  return true;
}

bool
unicode_font_rep::is_ot_integral (string s) {
  if (math_type != MATH_TYPE_OPENTYPE) return false;
  if (N (ot_integral) == 0) {
    array<string> integrals;
    integrals << string ("<int>") << string ("<iiint>") << string ("<iiiint>")
              << string ("<oint>") << string ("<oiint>") << string ("<oiiint>")
              << string ("<upint>") << string ("<upiint>")
              << string ("<upiiint>") << string ("<upiiiint>")
              << string ("<upoint>") << string ("<upoiint>")
              << string ("<upoiiint>") << string ("<intlim>")
              << string ("<iintlim>") << string ("<iiintlim>")
              << string ("<iiiintlim>") << string ("<ointlim>")
              << string ("<oiintlim>") << string ("<oiiintlim>");
    for (auto integral : integrals) {
      ot_integral << get_glyphID (integral);
    }
  }
  unsigned int glyphID= get_glyphID (s);
  // if variant, we turn to the base glyphID
  glyphID= math_table->get_init_glyphID (glyphID);
  return ot_integral->contains (glyphID);
}

font
unicode_font (string family, int size, int dpi) {
  return unicode_font (family, size, dpi, dpi);
}
